/*LICENSE_BEGIN
 * ******************************************************************
 * Copyright 2006 by the Dainamite Robocup-Team at the
 * DAI-Labor, TU-Berlin (www.dai-labor.de)
 * 
 * Members of Dainamite are:
 * Holger Endert
 * Robert Wetzker
 * Thomas Karbe
 * Axel Heßler
 * Philippe Bütter
 * Felix Brossmann
 * 
 * Licensed under the GNU GPL (General Public License) Free Software 
 * License, Version 2. You may use this file only in compliance with 
 * the License. You may obtain a copy of the License at:
 * 
 * http://www.fsf.org/licensing/licenses/gpl.txt
 * ******************************************************************
LICENSE_END*/
package robocup.component.worldobjects;

import java.util.EnumMap;
import java.util.LinkedList;

import robocup.component.ControllerNumbers;
import robocup.component.DAInamiteConstants;
import robocup.component.PConf;
import robocup.component.SConf;
import robocup.component.WorldModel;
import robocup.component.ControllerNumbers.PLAY_MODE;
import robocup.component.ControllerNumbers.TEAM;
import robocup.component.actions.Action;
import robocup.component.actions.DashAction;
import robocup.component.actions.MoveAction;
import robocup.component.actions.PointToAction;
import robocup.component.actions.TurnAction;
import robocup.component.actions.TurnNeckAction;
import robocup.component.geometry.Vektor;
import robocup.component.math.RCMath;
import robocup.component.speechacts.MessageFactory;
import robocup.component.speechacts.MessageFactory.MESSAGES;
import robocup.component.tactics.AbstractState;
import robocup.component.tactics.StateEvaluation;
import robocup.component.tactics.AbstractState.STATES;
import robocup.component.util.RingList;

/**
 * The WorldModel representation of a player. It provides a great amount
 * of methods for working with its data. Because players are movable, they
 * extend the behaviour of DynamicObject.
 */
public class Player extends DynamicObject {

	/**
	 * the place on field where the player usually is
	 */
	public enum ROLE {
		// default roles
		GOALIE, LEFT_BACK_FOUR, INLE_BACK_FOUR, INRE_BACK_FOUR, RIGHT_BACK_FOUR, LEFT_MIDFIELD, CENTER_MIDFIELD, RIGHT_MIDFIELD, LEFT_ATTACKER, CENTER_ATTACKER, RIGHT_ATTACKER, UNKNOWN,

		META_POSITION_RIGHT_WING, META_POSITION_LEFT_WING, META_POSITION_ATTACKER, META_POSITION_MIDFIELD, META_POSITION_DEFENSE;

		/**
		 * Checks wether a role conatins another.
		 * 
		 * @param r -
		 *            role
		 * @return true if role is contained, true if equals
		 */
		public boolean contains(ROLE r) {
			if (r == this)
				return true;

			switch (this) {
			case META_POSITION_ATTACKER:
				return r == LEFT_ATTACKER || r == CENTER_ATTACKER
						|| r == RIGHT_ATTACKER;

			case META_POSITION_MIDFIELD:
				return r == LEFT_MIDFIELD || r == CENTER_MIDFIELD
						|| r == RIGHT_MIDFIELD;

			case META_POSITION_DEFENSE:
				return r == LEFT_BACK_FOUR || r == INLE_BACK_FOUR
						|| r == INRE_BACK_FOUR || r == RIGHT_BACK_FOUR;

			case META_POSITION_LEFT_WING:
				return r == LEFT_ATTACKER || r == LEFT_MIDFIELD
						|| r == LEFT_BACK_FOUR;

			case META_POSITION_RIGHT_WING:
				return r == RIGHT_ATTACKER || r == RIGHT_MIDFIELD
						|| r == RIGHT_BACK_FOUR;

			}

			return false;
		}
	};

	/**
	 * A Mapping from roles to numbers (index in the array + 1)
	 */
	private ROLE[] numberToRoles = new ROLE[] { ROLE.GOALIE,
			ROLE.LEFT_BACK_FOUR, ROLE.INLE_BACK_FOUR, ROLE.INRE_BACK_FOUR,
			ROLE.RIGHT_BACK_FOUR, ROLE.LEFT_MIDFIELD, ROLE.CENTER_MIDFIELD,
			ROLE.RIGHT_MIDFIELD, ROLE.LEFT_ATTACKER, ROLE.CENTER_ATTACKER,
			ROLE.RIGHT_ATTACKER };

	/**
	 * what information is the last update based on
	 */
	public enum UPDATE_MODE {
		INIT, EXPECTED, HEARED, SEEN_FULL, SEEN_TEAM_OWN, SEEN_TEAM_OTHER, SEEN_NOTEAM,

		// SEEN + HEARED
		META_BASED_ON_INFO
	};

	private class HearInfo {
		private int number;

		private MESSAGES messageType;

		private int bodyCycle;

		/**
	     * Creates a new HearInfo
	     * 
		 * @param bodyCycle the bodyCycle of the player
		 * @param number the number of the heared player
		 * @param messageType the type of the message
		 */
		public HearInfo(int bodyCycle, int number,
				MessageFactory.MESSAGES messageType) {
			this.bodyCycle = bodyCycle;
			this.number = number;
			this.messageType = messageType;
		}

		/**
		 * @return the messageType.
		 */
		public MESSAGES getMessageType() {

			return this.messageType;
		}

		/**
		 * @param messageType
		 *            The messageType to set.
		 */
		public void setMessageType(MESSAGES messageType) {

			this.messageType = messageType;
		}

		/**
		 * @return the number.
		 */
		public int getNumber() {

			return this.number;
		}

		/**
		 * @param number
		 *            The number to set.
		 */
		public void setNumber(int number) {

			this.number = number;
		}

		/**
		 * @return the bodyCycle.
		 */
		public int getBodyCycle() {

			return this.bodyCycle;
		}

		/**
		 * @param bodyCycle
		 *            The bodyCycle to set.
		 */
		public void setBodyCycle(int bodyCycle) {

			this.bodyCycle = bodyCycle;
		}
	}

	private EnumMap<UPDATE_MODE, Integer> updateCycles = new EnumMap<UPDATE_MODE, Integer>(
			UPDATE_MODE.class);

	/**
	 * maps each state to a list of playmodes
	 */
	public EnumMap<STATES, LinkedList<PLAY_MODE>> knownStates = new EnumMap<STATES, LinkedList<PLAY_MODE>>(
			STATES.class);

	private RingList<TEAM> ballPossessions = new RingList<TEAM>(10);

	/**
	 * The players tricot number
	 */
	private int number;

	/**
	 * The body direction of the player.
	 */
	private double bodyDir;

	/**
	 * The neck-angle of the player.
	 */
	private double neckAngle;

	/**
	 * The point-direction of the player.
	 */
	private double pointDirection;

	/**
	 * The players current stamina. Note, that this is known only
	 * to the self-agent.
	 */
	private double stamina;

	/**
	 * The players current effort value. Note, that this is known only
	 * to the self-agent.
	 */
	private double effort;

	/**
	 * The players current recovery value. Note, that this is known only
	 * to the self-agent.
	 */
	private double recovery;

	/**
	 * The number of cycles, the agent is anable to act after tackling.
	 */
	private int tackledeadCountdown = 0;

	/**
	 * Determines if this player represents me or any player.
	 */
	private boolean isMe;

	/**
	 * Is the player a teammate?
	 */
	private boolean isFriend;

	/**
	 * Is the player agoalie?
	 */
	private boolean isGoalie = false;

	/**
	 * The players heterogeneous type.
	 */
	private PConf pConf;

	/**
	 * Used for calculations - do not change.
	 */
	private Vektor dummyVektor = new Vektor();

	/**
	 * Used for calculations - do not change.
	 */
	private Vektor dummyVektor2 = Vektor.getDummyInstance();

	/**
	 * Used for calculations - do not change.
	 */
	private double dummyDouble = 0;

	/**
	 * The player I listen to: Only known by the self-agent.
	 */
	private Player attention = null;

	/**
	 * Arm-Direction relative to body, reaches from
	 * -180 - 180 degree
	 */
	private double armDirection = 0;

	/**
	 * The pointing distance.
	 */
	private double armDistance = 0;

	/**
	 * The number of cycles, the arm stays at the given point.
	 */
	private int armExpires = 0;

	/**
	 * Is the arm movable again?
	 */
	private boolean armMovable = true;

	/**
	 * Does the players status changed (Stamina/PlayerType)
	 */
	private boolean statusChanged = false;

	/**
	 * The players last stamina status.
	 */
	private double lastStaminaStatus = 4000;

	/**
	 * The players last executed state.
	 */
	private STATES lastState = STATES.DUMMY_STATE;

	/**
	 * The number of the players role.
	 */
	private int roleNumber = -1;

	/**
	 * The last heared info of this player.
	 */
	private HearInfo lastHearedInfo = new HearInfo(-1, -1, MESSAGES.KICKED_BALL);

	/**
	 * The tactical device of the player. Only used for the self-agent.
	 */
	private StateEvaluation myStateEval = null;

	/**
	 * Flag, indicating if a player was seen offside on passing.
	 */
	private boolean wasOffsideWhenPassed = false;

	/**
	 * Flag, indicating that the player executed a standard action.
	 */
	private boolean kickedStandard = false;

	/**
	 * Specifies if an Vektor or a double angle should be returned relative to
	 * head,
	 * 
	 */
	public enum RELATIVE {
		TO_HEAD, TO_BODY
	}

	/**
	 * An object holding information about a player.
	 * 
	 * @param iCycle -
	 *            time, when he was seen last (real cycle)
	 * @param iBodyCycle -
	 *            time, when he was seen last (body-Cycle)
	 * @param iNumber -
	 *            the players tricot-number
	 * @param iPosAbs -
	 *            the players absolute position
	 * @param iSpeed -
	 *            the players speed
	 * @param iBodyDir -
	 *            the players body direction
	 * @param iNeckAngle -
	 *            the players neck-angle
	 * @param iStamina -
	 *            the players stamina
	 * @param iEffort -
	 *            the players effort
	 * @param iRecovery -
	 *            the players recovery
	 * @param iIsMe -
	 *            whether it is the self-agent or not
	 * @param isFriend -
	 *            whether it is a teammate or not
	 * @param goalie -
	 *            whether it is a goalie or not
	 * @param pConf -
	 *            his heterogeneous attributes
	 */
	public Player(int iCycle, int iBodyCycle, int iNumber, Vektor iPosAbs,
			Vektor iSpeed, double iBodyDir, double iNeckAngle, double iStamina,
			double iEffort, double iRecovery, boolean iIsMe, boolean isFriend,
			boolean goalie, PConf pConf) {

		super(iCycle, iBodyCycle, iPosAbs, iSpeed);
		this.number = iNumber;
		this.bodyDir = iBodyDir;
		this.neckAngle = iNeckAngle;
		this.stamina = iStamina;
		this.effort = iEffort;
		this.recovery = iRecovery;
		this.isMe = iIsMe;
		this.isFriend = isFriend;
		this.isGoalie = goalie;
		this.pConf = pConf;

	}

	/**
	 * this method creates a new player. use it, if you wan't to override the
	 * attributes anyway.
	 * 
	 * @return a new player with default values
	 */
	public static Player getDummyInstance() {

		return new Player(-1, -1, -1, new Vektor(), new Vektor(), 0, 0, 4000.0,
				1.0, 1.0, true, true, false, null);
	}

	/**
	 * this method creates an array with dummy players.
	 * 
	 * @param number -
	 *            array length
	 * @return array of dummy #number players
	 */
	public static Player[] getDummyInstances(int number) {

		Player[] player = new Player[number];
		for (int i = 0; i < player.length; i++) {
			player[i] = Player.getDummyInstance();
		}
		return player;
	}

	/**
	 * Clones the Player.
	 * 
	 * @return The Player clone
	 */
	public Player cloned() {

		Player newPlayer = Player.getDummyInstance();
		newPlayer.copy(this);
		return newPlayer;
	}

	/**
	 * Copies a play into "this" player avoiding player creation ;-).
	 * 
	 * @param p -
	 *            original player
	 */
	public void copy(Player p) {

		if (p == null)
			return;
		this.setCycle(p.getCycle());
		this.setBodyCycle(p.getBodyCycle());
		this.setNumber(p.getNumber());
		this.getPosition().x = p.getPosition().x;
		this.getPosition().y = p.getPosition().y;
		this.getSpeed().x = p.getSpeed().x;
		this.getSpeed().y = p.getSpeed().y;
		this.setBodyDir(p.getBodyDir());
		this.setNeckAngle(p.getNeckAngle());
		this.setStamina(p.getStamina());
		this.setEffort(p.getEffort());
		this.setRecovery(p.getRecovery());
		this.isMe = p.isMe;
		this.isFriend = p.isFriend;
		this.isGoalie = p.isGoalie;
		this.pConf = p.pConf;
		this.setReliable(p.isReliable());
		this.setSpeedReliable(p.speedIsReliable());
		this.updateCycles = p.updateCycles.clone();
		this.tackledeadCountdown = p.tackledeadCountdown;
		this.myStateEval = p.getMyStateEval();
		this.knownStates = p.knownStates;
		this.lastState = p.lastState;
		this.roleNumber = p.getRoleNumber();
	}

	/**
	 * sets the update mode. this is used to determine when the last update of a
	 * certain update occured. as this is use to tell what information of a
	 * certain type arrived when there are some modes which contain other as
	 * they include the other modes informational content.
	 * 
	 * @param mode -
	 *            the update mode
	 * @param bodyCycle -
	 *            the body cycle of the update
	 */
	public void setUpdateMode(UPDATE_MODE mode, int bodyCycle) {
		this.updateCycles.put(mode, new Integer(bodyCycle));

		// some updates contain others
		if (mode == UPDATE_MODE.SEEN_FULL) {
			this.updateCycles.put(UPDATE_MODE.SEEN_NOTEAM, new Integer(
					bodyCycle));
			this.updateCycles.put(UPDATE_MODE.SEEN_TEAM_OTHER, new Integer(
					bodyCycle));
			this.updateCycles.put(UPDATE_MODE.SEEN_TEAM_OWN, new Integer(
					bodyCycle));
		} else if (mode == UPDATE_MODE.SEEN_TEAM_OTHER
				|| mode == UPDATE_MODE.SEEN_TEAM_OWN) {
			this.updateCycles.put(UPDATE_MODE.SEEN_NOTEAM, new Integer(
					bodyCycle));
		}
	}

	/**
	 * @param mode the update mode
	 * @return the last cycle with this update mode
	 */
	public Integer getUpdateCycle(UPDATE_MODE mode) {
		return this.updateCycles.get(mode);
	}

	/**
	 * the age of the player depending on the update type.
	 * 
	 * @param currentBodyCycle
	 * @param mode
	 * @return the age (wm.getBodyCycle - cycle of last update of given type)
	 */
	public int getAge(int currentBodyCycle, UPDATE_MODE mode) {

		if (mode == UPDATE_MODE.META_BASED_ON_INFO) {
			return Math.min(getAge(currentBodyCycle, UPDATE_MODE.SEEN_NOTEAM),
					getAge(currentBodyCycle, UPDATE_MODE.HEARED));
		}

		Integer i = this.updateCycles.get(mode);
		if (i == null)
			return Integer.MAX_VALUE;

		if (mode == UPDATE_MODE.INIT) {
			return 0;
		}

		return currentBodyCycle - i.intValue();
	}

	/**
	 * @return true if the update is based on estimation
	 */
	public boolean isPredicted() {
		if (this.getUpdateCycle(UPDATE_MODE.EXPECTED) == null)
			return false;

		return this.getUpdateCycle(UPDATE_MODE.EXPECTED) > this.getBodyCycle();
	}

	/** (non-Javadoc)
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	public boolean equals(Object o) {
		if (o instanceof Player) {
			Player p = (Player) o;

			return p.number == this.number && p.isFriend == this.isFriend;
		}
		;

		return false;

	}

	/** ****************** geometry stuff ************* */

	/**
	 * Returns an absolute position denoted by the relative position of the
	 * specified StaticObject <code>o</code>. The enum
	 * <code>fromPosRelToHead</code> specifies if the position of the
	 * StaticObject <code>o</code> is relative to the head or relative to the
	 * body.
	 * 
	 * @param o -
	 *            StaticObject
	 * @param relative -
	 *            specifies if the relative position of the specified
	 *            StaticObject <code>o</code> is relative to the head or
	 *            relative to the body
	 * 
	 * @return Vektor - absolute position
	 */
	public final Vektor getPosAbs(StaticObject o, RELATIVE relative) {

		return getPosAbs(o.getPosition(), relative);
	}

	/**
	 * Returns an absolute position denoted by the specified relative Vektor
	 * <code>relativePosition</code>. The enum <code>relative</code>
	 * specifies if the Vektor <code>relativePosition</code> is relative to
	 * the head or relative to the body.
	 * 
	 * @param relativePosition -
	 *            Vektor
	 * @param relative -
	 *            specifies if the Vektor <code>relativePosition</code> is
	 *            relative to the head or relative to the body
	 * 
	 * @return Vektor - absolute position
	 */
	public final Vektor getPosAbs(Vektor relativePosition, RELATIVE relative) {

		Vektor v = relativePosition.cloned();
		if (relative == RELATIVE.TO_HEAD) {
			v.rotate(getHeadDir());
		} else if (relative == RELATIVE.TO_BODY) {
			v.rotate(getBodyDir());
		} else {
			// error
			System.out
					.println("[ERROR] Player#getPosAbs(Vektor, RELATIVE): relative="
							+ relative);
			return new Vektor();
		}
		return v.add(getPosition());
	}

	/**
	 * Returns a relative position from the absolute position of the
	 * StaticObject <code>o</code> either relative to the head or relative to
	 * the body. This is specified by the enum <code>relative</code>.
	 * 
	 * @param o -
	 *            StaticObject
	 * @param relative -
	 *            if <code>relative</code> is RELATIVE.TO_HEAD the returned
	 *            Vektor will be relative to the head, if it is RELATIVE.TO_BODY
	 *            the returned Vektor will be relative to the body
	 * 
	 * @return Vektor - position either relative to the head or relative to the
	 *         body
	 */
	public final Vektor getPosRel(StaticObject o, RELATIVE relative) {

		return getPosRel(o.getPosition(), relative);
	}

	/**
	 * Returns a relative position denoted by the specified absolute Vektor
	 * <code>absolutePosition</code>. The enum <code>relative</code>
	 * specifies if the returned Vektor should be relative to the head or
	 * relative to the body.
	 * 
	 * @param absolutePosition -
	 *            Vektor
	 * @param relative -
	 *            if <code>relative</code> is RELATIVE.TO_HEAD the returned
	 *            Vektor will be relative to the head, if it is RELATIVE.TO_BODY
	 *            the returned Vektor will be relative to the body
	 * 
	 * @return Vektor - position either relative to the body or to the head
	 */
	public final Vektor getPosRel(Vektor absolutePosition, RELATIVE relative) {

		Vektor v = absolutePosition.sub(getPosition());
		if (relative == RELATIVE.TO_HEAD) {
			v.rotate(-getHeadDir());
		} else if (relative == RELATIVE.TO_BODY) {
			v.rotate(-getBodyDir());
		} else {
			// error
			System.out
					.println("[ERROR] Player#getPosRel(Vektor, RELATIVE): relative="
							+ relative);
			return new Vektor();
		}
		return v;
	}

	/**
	 * Returns the absolute angle to the relative position of the specified
	 * StaticObject <code>o</code>. If the position of the StaticObjct
	 * <code>o</code> is relative to the head the enum <code>relative</code>
	 * should be RELATIVE.TO_HEAD, else is should be RELATIVE.TO_BODY.
	 * 
	 * @param o -
	 *            StaticObject with relative position
	 * @param relative -
	 *            specifies if the Vektor <code>relativePosition</code> is
	 *            relative to the head or relative to the body
	 * 
	 * @return double - absolute angle [-180..180]
	 */
	public final double getAngleAbs(StaticObject o, RELATIVE relative) {

		return getAngleAbs(o.getPosition(), relative);
	}

	/**
	 * Returns the absolute angle to the relative Vektor
	 * <code>relativePosition</code>. If the Vektor is relative to the head
	 * the enum <code>relative</code> should be RELATIVE.TO_HEAD, else is
	 * should be RELATIVE.TO_BODY.
	 * 
	 * @param relativePosition -
	 *            Vektor either relative to the head or relative to the body
	 * @param relative -
	 *            specifies if the Vektor <code>relativePosition</code> is
	 *            relative to the head or relative to the body
	 * 
	 * @return double - absolute angle [-180..180]
	 */
	public final double getAngleAbs(Vektor relativePosition, RELATIVE relative) {

		return getPosAbs(relativePosition, relative).getAngle();
	}

	/**
	 * Returns the relative angle from the absolute position of the StaticObject
	 * <code>o</code>. If the returned angle should be relative to the head
	 * the enum <code>relative</code> should be RELATIVE.TO_HEAD, else is
	 * should be RELATIVE.TO_BODY.
	 * 
	 * @param o -
	 *            StaticObject o with absolute position
	 * @param relative -
	 *            specifies if the returned angle will be relative to the head
	 *            or relative to the body
	 * 
	 * @return double - relative angle either relative to the head or relative
	 *         to the body [-180..180]
	 */
	public final double getAngleRel(StaticObject o, RELATIVE relative) {

		return getAngleRel(o.getPosition(), relative);
	}

	/**
	 * Returns the relative angle to the absolute Vektor
	 * <code>absolutePosition</code>. If the returned Vektor should be
	 * relative to the head the enum <code>relative</code> should be
	 * RELATIVE.TO_HEAD, else is should be RELATIVE.TO_BODY.
	 * 
	 * @param absolutePosition -
	 *            absolute Vektor
	 * @param relative -
	 *            specifies if the returned angle will be relative to the head
	 *            or relative to the body
	 * 
	 * @return double - relative angle either relative to the head or relative
	 *         to the body [-180..180]
	 */
	public final double getAngleRel(Vektor absolutePosition, RELATIVE relative) {

		return getPosRel(absolutePosition, relative).getAngle();
	}

	/**
	 * Returns the absolute angle from the specified relative angle
	 * <code>relativeAngle</code>, which is either relative to the head or
	 * relative to the body. This is specified by the enum <code>relative</code>.
	 * 
	 * @param relativeAngle -
	 *            double angle either relative to the head or relative to the
	 *            body
	 * @param relative -
	 *            specifies if the angle <code>relativeAngle</code> is
	 *            relative to the head or relative to the body
	 * 
	 * @return double - absolute angle [-180..180]
	 */
	public final double getAngleAbs(double relativeAngle, RELATIVE relative) {

		if (relative == RELATIVE.TO_HEAD) {
			return Vektor.normalize(relativeAngle + getHeadDir());
		} else if (relative == RELATIVE.TO_BODY) {
			return Vektor.normalize(relativeAngle + getBodyDir());
		} else {
			System.out.println("Player.java: relative=" + relative);
			return .0;
		}
	}

	/**
	 * Returns the relative angle from the specified absolute angle
	 * <code>absoluteAngle</code>. If the returned angle should be relative
	 * to the head the enum <code>relative</code> should be RELATIVE.TO_HEAD,
	 * else if should be RELATIVE.TO_BODY.
	 * 
	 * @param absoluteAngle -
	 *            absolute angle
	 * @param relative -
	 *            specifies if the returned angle will be relative to the head
	 *            or relative to the body
	 * 
	 * @return double - relative angle either relative to the head or relative
	 *         to the body [-180..180]
	 */
	public final double getAngleRel(double absoluteAngle, RELATIVE relative) {

		if (relative == RELATIVE.TO_HEAD) {
			return Vektor.normalize(absoluteAngle - getHeadDir());
		} else if (relative == RELATIVE.TO_BODY) {
			return Vektor.normalize(absoluteAngle - getBodyDir());
		} else {
			// error
			System.out
					.println("Player#getAngleRel(double, RELATIVE): relative="
							+ relative);
			return .0;
		}
	}

	/**
	 * predicts the player in the next cycle based on an action. returns a
	 * clone.
	 * 
	 * @param a the action to predict
	 * @return the player after the action
	 */
	public Player predictPlayerAfterAction(Action a) {

		Player p = this.cloned();
		p.predictThisAfterAction(a);
		return p;
	}

	/**
	 * predicts the player in the next cycle based on an action. working on the
	 * reference!!
	 * 
	 * @param a the action to predict
	 */
	public void predictThisAfterAction(Action a) {

		if (a == null || (this.tackledeadCountdown > 0 && a.getType().isMain())) {
			this.extrapolate(1, this.getPConf().PLAYER_DECAY);
			if (this.tackledeadCountdown > 0) {
				this.tackledeadCountdown--;
			}
			this.lastState = STATES.DUMMY_STATE;
			return;
		}

		if (a.getType().isMain()) {
			this.setUpdateMode(UPDATE_MODE.EXPECTED, this
					.getUpdateCycle(UPDATE_MODE.EXPECTED) == null ? this
					.getBodyCycle() + 1
					: Math.max(this.getUpdateCycle(UPDATE_MODE.EXPECTED), this
							.getBodyCycle()) + 1);
			this.lastState = STATES.DUMMY_STATE;
		}

		switch (a.getType()) {
		case CHANGE_VIEW_MODE:
			return;

		case MOVE:
			MoveAction ma = (MoveAction) a;

			Vektor next = new Vektor(Vektor.XY, ma.getX(), ma.getY());
			next.rotate(90);
			next.y = -next.y;
			this.setPosition(next);
			this.setSpeed(new Vektor(0, 0));
			break;

		case TURN_NECK:
			double newNeckAngle = Vektor.normalize(this.getNeckAngle()
					+ ((TurnNeckAction) a).getAngle());
			if (newNeckAngle < SConf.getInstance().MINNECKANG) {
				newNeckAngle = SConf.getInstance().MINNECKANG;
			} else if (newNeckAngle > SConf.getInstance().MAXNECKANG) {
				newNeckAngle = SConf.getInstance().MAXNECKANG;
			}
			this.setNeckAngle(newNeckAngle);
			// no stamina increase as it is only a second action
			return;

		case DASH:
			this.predictThisAfterDashAction((DashAction) a);
			break;

		case TURN:
			this.predictThisAfterTurnAction((TurnAction) a);
			break;

		case KICK:
			this.extrapolate(1, this.getPConf().PLAYER_DECAY);
			break;

		case CATCH_BALL:
			this.extrapolate(1, this.getPConf().PLAYER_DECAY);
			return;

		case TACKLE:
			this.extrapolate(1, this.getPConf().PLAYER_DECAY);
			this.tackledeadCountdown = 10;
			return;

		case POINTTO:
			this.predictThisAfterPointToAction((PointToAction) a);
			break;

		case SAY:
			return;

		case ATTENTIONTO:
			return;
		default:
			System.out.println(this.getCycle() + " P:" + this.getNumber()
					+ " got strange Action:" + a.getType());
			return;
		}

		if (!a.getType().isMain())
			return;

		// BEGIN MT Alg. 3.1: stamina model algorithm

		// reduce recovery when stamina is below recovery decrement threshold
		if (this.stamina <= SConf.getInstance().RECOVER_DEC_THR
				* SConf.getInstance().STAMINA_MAX) {
			this.recovery -= SConf.getInstance().RECOVER_DEC;
			this.recovery = Math.max(SConf.getInstance().RECOVER_MIN,
					this.recovery);
		}

		// reduce effort when stamina is below effort decrement threshold
		if (this.stamina <= SConf.getInstance().EFFORT_DEC_THR
				* SConf.getInstance().STAMINA_MAX) {
			this.effort -= SConf.getInstance().EFFORT_DEC;
			this.effort = Math.max(this.getPConf().EFFORT_MIN, this.effort);
		}

		// increase effort when stamina is above effort increment threshold
		if (this.stamina >= SConf.getInstance().EFFORT_INC_THR
				* SConf.getInstance().STAMINA_MAX) {
			this.effort += SConf.getInstance().EFFORT_INC;
			this.effort = Math.min(this.getPConf().EFFORT_MAX, this.effort);
		}

		// restore stamina according to recovery value
		this.stamina = Math.min(SConf.getInstance().STAMINA_MAX, this.stamina
				+ this.recovery * this.getPConf().STAMINA_INC_MAX);

		// END MT Alg. 3.1: stamina model algorithm
	}

	/**
	 * predicts the player when his action is based on a given state.
	 * 
	 * @param state -
	 *            the state
	 */
	public void predictThisAfterState(AbstractState state) {
		state.calculateMainAction();

		this.predictThisAfterAction(state.getMainAction());
		this.lastState = state.getState();

		if (this.isMe) {
			state.calculateTurnNeckAction();
			state.calculateViewModeAction();
			this.predictThisAfterAction(state.getTurnNeckAction());
			this.predictThisAfterAction(state.getViewModeAction());
		}

	}

	/**
	 * To avoid castings, when an action is known to be a pointo-action, use
	 * this method instead!
	 * 
	 * @param a -
	 *            the dashaction
	 */
	private void predictThisAfterPointToAction(PointToAction a) {

		if (a == null)
			return;

		double dir = a.getDir();

		if ((dir > 180) || (dir < -180))
			return;

		this.setPointDirection(dir);

	}

	/**
	 * To avoid castings, when an action is known to be a dash-action, use this
	 * method instead!
	 * 
	 * @param a -
	 *            the dashaction
	 */
	private void predictThisAfterDashAction(DashAction a) {

		if (a == null)
			return;

		double power = a.getPower();
		if (power > SConf.getInstance().MAXPOWER) {
			power = SConf.getInstance().MAXPOWER;
		} else if (power < SConf.getInstance().MINPOWER) {
			power = SConf.getInstance().MINPOWER;
		}
		double accel = power * this.getPConf().DASH_POWER_RATE
				* this.getEffort();
		// is this needed?
		if (accel > this.getPConf().PLAYER_ACCEL_MAX) {
			accel = this.getPConf().PLAYER_ACCEL_MAX;
		}

		this.dummyVektor.pointAtPolar(accel, this.getBodyDir());
		this.getSpeed().addToThis(this.dummyVektor);

		if (this.getSpeed().getLength() > this.getPConf().PLAYER_SPEED_MAX) {
			this.getSpeed().setLength(this.getPConf().PLAYER_SPEED_MAX);
		}

		this.extrapolate(1, this.getPConf().PLAYER_DECAY);

		// BEGIN reducing stamina according to
		// MT Alg. 3.1: stamina model algorithm

		// reduce stamina according to dash power
		if (power < 0) {
			this.stamina -= 2 * Math.abs(power);
		} else {
			this.stamina -= power;
		}
	}

	/**
	 * To avoid castings, when an action is known to be a turn-action, use this
	 * method instead!
	 * 
	 * @param a -
	 *            the turn-action
	 */
	private void predictThisAfterTurnAction(TurnAction a) {

		if (a == null)
			return;

		double turnAngle = a.getAngle();

		if (turnAngle > SConf.getInstance().MAXMOMENT)
			turnAngle = SConf.getInstance().MAXMOMENT;
		else if (turnAngle < SConf.getInstance().MINMOMENT)
			turnAngle = SConf.getInstance().MINMOMENT;

		double turnAngleEff = turnAngle
				/ (1.0 + this.getPConf().INERTIA_MOMENT
						* this.getSpeed().getLength());
		this.setBodyDir(Vektor.normalize(this.getBodyDir() + turnAngleEff));
		this.extrapolate(1, this.getPConf().PLAYER_DECAY);
	}

	/**
	 * this method limits the dash power of a dash action to a certain
	 * Stamina-Level.
	 * 
	 * @param action
	 * @param level
	 */
	public void correctDashPower(Action action,
			ControllerNumbers.STAMINA_LEVEL level) {

		if (action.getType() != Action.TYPE.DASH) {
			return;
		}

		DashAction dash = (DashAction) action;

		double stamina_thr = 0.0;
		switch (level) {
		case MAX:
			stamina_thr = DAInamiteConstants.STAMINA_THR_MAX;
			break;
		case HIGH:
			stamina_thr = DAInamiteConstants.STAMINA_THR_HIGH;
			break;
		case MEDIUM:
			stamina_thr = DAInamiteConstants.STAMINA_THR_MID;
			break;
		case MEDIUM_LOW:
			stamina_thr = DAInamiteConstants.STAMINA_THR_MID_LOW;
			break;
		case LOW:
			stamina_thr = DAInamiteConstants.STAMINA_THR_LOW;
			break;
		case ZERO:
			stamina_thr = 0.0;
		}

		double maxDashPower;
		if (this.getStamina() > stamina_thr) {
			maxDashPower = this.getStamina() - stamina_thr
					+ this.getPConf().STAMINA_INC_MAX * this.getRecovery()
					* 0.8;
		} else {
			maxDashPower = (this.getStamina() / SConf.getInstance().STAMINA_MAX)
					* (this.getPConf().STAMINA_INC_MAX * this.getRecovery())
					* 0.8;
		}
		if (dash.getPower() < 0) {
			maxDashPower = -Math.min(-dash.getPower(), maxDashPower / 2.0);
		} else {
			maxDashPower = Math.min(dash.getPower(), maxDashPower);
		}
		dash.setPower((int) maxDashPower);
	}

	/**
	 * ***************************************** Robo - Physics
	 * ***************************************************
	 */
	/**
	 * This method calculates the actual power rate for a kick command
	 * considering the ball's location relative to the player.
	 * 
	 * @param b -
	 *            ball to kick
	 * 
	 * @return the actual power rate
	 */
	public double getActualKickPowerRate(Ball b) {

		double distance = this.getDistance(b) - this.getPConf().PLAYER_SIZE
				- SConf.getInstance().BALL_SIZE;

		// ball seen inside the player (with noise)
		if (distance < -0.005) {
			// shouldn't occur when ball.updateBallWhenInsideMe() is used
			throw new RuntimeException(
					this.getCycle()
							+ ". Player:"
							+ this.getNumber()
							+ " Ball seen inside Player in Player.getActualKickRate Ball:"
							+ b.toString() + " P:"
							+ this.getPosition().toString() + distance);
		}

		// can't kick
		if (distance > this.getPConf().KICKABLE_MARGIN
				+ SConf.getInstance().MARGIN_NOISE) {
			throw new RuntimeException(
					this.getCycle()
							+ ". Player:"
							+ this.getNumber()
							+ " "
							+ b
							+ " out of range in Player.getActualKickRate -> no kicking (try to correct canKick())"
							+ distance + " " + this.getPConf().KICKABLE_MARGIN);
		}

		double angle = Math.abs(this.getAngleRel(b, RELATIVE.TO_BODY));

		return SConf.getInstance().KICK_POWER_RATE
				* (1 - 0.25 * (angle / 180) - 0.25 * (distance / this
						.getPConf().KICKABLE_MARGIN));
	}

	/**
	 * the max ball acceleration when kicking to a given point.
	 * 
	 * @param b -
	 *            ball
	 * @param posToKickTo -
	 *            point to kick to
	 * @return - max ball accel (-1 if not poss)
	 */
	public double getMaxBallAccel(Ball b, Vektor posToKickTo) {

		this.dummyVektor.copy(posToKickTo);
		this.dummyVektor.subFromThis(b.getPosition());
		this.dummyVektor.setLength(1.0);

		return this.getMaxBallAccelOptimized(b, this.dummyVektor);
	}

	/**
	 * the max ball acceleration given the normalized dir to kick to. (that's
	 * more efficient as the Vektor-operations are prevented)
	 * 
	 * @param b -
	 *            ball
	 * @param dirToKickTo -
	 *            abs. direction to kick to given as an Vektor of length=1
	 * @return the max ballAcceleration
	 */
	public double getMaxBallAccelOptimized(Ball b, Vektor dirToKickTo) {

		double maxKickAccel = this.getActualKickPowerRate(b)
				* SConf.getInstance().MAXPOWER;

		double term = b.getSpeed().scalar(dirToKickTo);
		double sqrt_term = maxKickAccel * maxKickAccel
				- Math.pow(b.getSpeed().getLength(), 2) + Math.pow(term, 2);

		if (sqrt_term < 0)
			return -1;

		term += Math.sqrt(sqrt_term);

		if (term < 0.0)
			return -1;

		term = Math.min(SConf.getInstance().BALL_SPEED_MAX, term);

		return term;
	}

	/**
	 * the max ballAccel as a Vektor.
	 * 
	 * @param b
	 * @param posToKickTo
	 * @return the max ballAcceleration
	 */
	public Vektor getMaxBalAccelVektor(Ball b, Vektor posToKickTo) {

		this.dummyVektor.copy(posToKickTo);
		this.dummyVektor.subFromThis(b.getPosition());
		this.dummyVektor.setLength(1.0);

		this.dummyDouble = this.getMaxBallAccelOptimized(b, this.dummyVektor);
		if (this.dummyDouble <= 0)
			return null;

		return new Vektor(this.dummyDouble, this.dummyVektor.getAngle());
	}

	/**
	 * ***************************************** simple Getter/Setter
	 * *********************************************
	 */

	/**
	 * Determines if this player is me or any player.
	 * 
	 * @return true if this player is me, false if not.
	 */
	public final boolean isMe() {

		return this.isMe;
	}

	/**
	 * @return true if the player is a teammate, else false
	 */
	public final boolean isFriend() {

		return this.isFriend;
	}

	/**
	 * Player is goalie
	 * 
	 * @author rw
	 * @return true if the player is goalie
	 */
	public final boolean isGoalie() {

		return this.isGoalie;
	}

	/**
	 * @return Returns the bodyDir.
	 */
	public final double getBodyDir() {

		return this.bodyDir;
	}

	/**
	 * @return Returns the headDir.
	 */
	public final double getHeadDir() {

		return Vektor.normalize(this.getBodyDir() + this.getNeckAngle());
	}

	/**
	 * @return Returns the number.
	 */
	public final double getNeckAngle() {

		return this.neckAngle;
	}

	/**
	 * @return Returns the number.
	 */
	public final int getNumber() {

		return this.number;
	}

	/**
	 * @return double - the current player's stamina
	 */
	public final double getStamina() {

		return this.stamina;
	}

	/**
	 * @return double - the current player's effort
	 */
	public final double getEffort() {

		return this.effort;
	}

	/**
	 * @return double - the current player's stamina recovery rate
	 */
	public final double getRecovery() {

		return this.recovery;
	}

	/**
	 * @param bodyDir
	 *            The bodyDir to set.
	 */
	public void setBodyDir(double bodyDir) {

		this.bodyDir = bodyDir;
	}

	/**
	 * @param isFriend
	 *            The isFriend to set.
	 */
	public void setFriend(boolean isFriend) {

		this.isFriend = isFriend;
	}

	/**
	 * @param isMe
	 *            The isMe to set.
	 */
	public void setMe(boolean isMe) {

		this.isMe = isMe;
	}

	/**
	 * @param neckAngle
	 *            The neckAngle to set.
	 */
	public void setNeckAngle(double neckAngle) {

		this.neckAngle = neckAngle;
	}

	/**
	 * @param number
	 *            The number to set.
	 */
	public void setNumber(int number) {

		this.number = number;
	}

	/**
	 * @param stamina -
	 *            the stamina to set
	 */
	public void setStamina(double stamina) {
		// if the new stamina differs much - mark agent as changed-status
		if (Math.abs(stamina - this.lastStaminaStatus) > DAInamiteConstants.STAMINA_STATUS_DIFF
				&& this.isMe == false) {
			this.statusChanged = true;
			this.lastStaminaStatus = stamina;
		}
		this.stamina = stamina;
	}

	/**
	 * @param effort -
	 *            the effort to set
	 */
	public void setEffort(double effort) {

		this.effort = effort;
	}

	/**
	 * @param recovery -
	 *            the stamina recovery rate to set
	 */
	public void setRecovery(double recovery) {

		this.recovery = recovery;
	}

	/** (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {

		return "Player C:" + this.getCycle() + " BC:" + getBodyCycle()
				+ ". Nr:" + this.getNumber() + " Pos:" + this.getPosition()
				+ " Vel:" + this.getSpeed() + " HA:" + this.getHeadDir()
				+ " BA:" + this.getBodyDir() + " ME:" + this.isMe + " Friend:"
				+ this.isFriend + " Reliable:" + this.isReliable();
	}

	/**
	 * @param isGoalie
	 *            The isGoalie to set.
	 */
	public void setGoalie(boolean isGoalie) {

		this.isGoalie = isGoalie;
	}

	/**
	 * players pConf.
	 * 
	 * @return the players configuration
	 */
	public PConf getPConf() {
		if (pConf == null) {
			pConf = new PConf();
		}
		return this.pConf;
	}

	/**
	 * set players pConf. (May be used for het. players)
	 * 
	 * @param conf
	 */
	public void setPConf(PConf conf) {
		this.pConf = conf;
	}

	/**
	 * a players kick distance
	 * 
	 * @return a players kick distance
	 */
	public double getKickDistance() {

		return this.pConf.KICKABLE_MARGIN + this.pConf.PLAYER_SIZE
				+ SConf.getInstance().BALL_SIZE;
	}

	/**
	 * a players catch distance
	 * 
	 * @return a players catch distance (0 if no goalie)
	 */
	public double getCatchDistance() {

		if (!this.isGoalie)
			return 0.0;
		return SConf.getInstance().CATCHABLE_AREA_L
				+ SConf.getInstance().BALL_SIZE;
	}

	/**
	 * the default role based on the players number.
	 * 
	 * @return the default role of the player
	 */
	public ROLE getDefaultRole() {
		return getRole(this.number);
	}

	/**
	 * @return the role based on the players ROLE(!) number
	 */
	public ROLE getRoleByRoleNumber() {
		return getRole(this.roleNumber);
	}

	/**
	 * Returns the default role for the player number
	 * 
	 * @param playernumber the number of the player
	 * @return the default role
	 */
	private ROLE getRole(int playernumber) {

		if (playernumber < 1 || playernumber > 11)
			return ROLE.UNKNOWN;

		return this.numberToRoles[playernumber - 1];
	}

	/**
   * makes this player to hear to another player
	 * @param player the player to hear to
	 */
	public void setAttentionTo(Player player) {

		this.attention = player;
	}

	/**
	 * @return the player to hear to
	 */
	public Player getAttentionTo() {

		return this.attention;
	}

	/**
	 * the max turning angle turning 1x given the players speed and
	 * INERTIA_MOMENT.
	 * 
   * @return the max turn angle after 1 turn
	 */
	public double getMaxTurningAngle1() {

		return 180.0 / (1 + this.getSpeed().getLength()
				* this.getPConf().INERTIA_MOMENT);
	}

	/**
	 * the max turning angle turning 2x given the players speed and
	 * INERTIA_MOMENT.
	 * 
   * @return the max turn angle after 2 turns
	 */
	public double getMaxTurningAngle2() {

		return this.getMaxTurningAngle1()
				+ 180.0
				/ (1 + this.getSpeed().getLength()
						* this.getPConf().PLAYER_DECAY
						* this.getPConf().INERTIA_MOMENT);
	}

	/**
	 * This method calculates the angle that has to be given to a turn command
	 * in order to turn the body by a given angle.
	 * 
	 * @param angle
	 *            The angle the player is supposed to turn his body by.
	 * @return double
	 */
	public double getAngleForTurn(double angle) {

		double speed = this.getSpeed().getLength();
		double turnAngle = angle
				* (1.0 + this.getPConf().INERTIA_MOMENT * speed);
		if (turnAngle > SConf.getInstance().MAXMOMENT)
			turnAngle = SConf.getInstance().MAXMOMENT;
		if (turnAngle < SConf.getInstance().MINMOMENT)
			turnAngle = SConf.getInstance().MINMOMENT;

		return turnAngle;
	}

	/**
	 * Succ_Prob=1 - ( player_2_ball.x / tackle_dist ) exp 6 + ( abs (
	 * player_2_ball.y ) / 1.25 ) exp 6 where tackle_dist is 2.0 when the ball
	 * is in front of the player, 0.5 otherwise.
	 * 
	 * player_2_ball is the vector from the player to the ball relative to the
	 * player's body direction.
	 * 
	 * @param ballPosition
	 *            the position of the ball
	 * @return the probability that tackle action will work
	 */
	public final double tackleSuccessProbability(Vektor ballPosition) {
		return 1 - tackleFailureProbability(ballPosition);
	}

	/**
	 * Fail_Prob=( player_2_ball.x / tackle_dist ) exp 6 + ( abs (
	 * player_2_ball.y ) / 1.25 ) exp 6 where tackle_dist is 2.0 when the ball
	 * is in front of the player, 0.5 otherwise.
	 * 
	 * player_2_ball is the vector from the player to the ball relative to the
	 * player's body direction.
	 * 
	 * @param ballPosition
	 *            the position of the ball
	 * @return the probability that tackle action will fail
	 */
	public final double tackleFailureProbability(Vektor ballPosition) {

		// player_2_ball is the vector from the player to the ball relative to
		// the player's body direction.
		Vektor player2Ball = position.cloned().getVektorTo(ballPosition);
		player2Ball.rotate(-bodyDir);

		SConf sconf = SConf.getInstance();

		// Check if ball is out of tackle range then stop calculation
		if (player2Ball.y > sconf.TACKLE_DIST
				|| player2Ball.y < -sconf.TACKLE_BACK_DIST
				|| player2Ball.x > sconf.TACKLE_WIDTH
				|| player2Ball.x < -sconf.TACKLE_WIDTH)
			return 1;

		// ATTENTION, X and Y must stay swapped because of our coordinate system
		double result = Math.pow(player2Ball.y
				/ (player2Ball.y > 0 ? sconf.TACKLE_DIST
						: sconf.TACKLE_BACK_DIST), sconf.TACKLE_EXPONENT)
				+ Math.pow(Math.abs(player2Ball.x) / sconf.TACKLE_WIDTH,
						sconf.TACKLE_EXPONENT);

		// Sometimes probability is a bit greater than 1,
		// and may be smaller than 0, if tackle_exponent is odd number,
		// just to be sure
		if (result > 1)
			return 1;
		else if (result < 0)
			return 0;
		else
			return result;
	}

	/**
	 * @return the tackledeadCountdown.
	 */
	public int getTackledeadCountdown() {

		return this.tackledeadCountdown;
	}

	/**
	 * Tells if the player has the ball in its kickable margin with noise as
	 * security.
	 * 
	 * @param b -
	 *            ball
	 * @return true if kickable and not tackle dead and ball reliable
	 */
	public boolean canKick(Ball b) {
		return canKick(b, 0);// SConf.getInstance().MARGIN_NOISE);
	}

	/**
	 * tells if the player has the ball in its kickable margin
	 * 
	 * @param b
	 *            the ball
	 * @param security
	 *            kickable area will be reduced by security when determining if
	 *            the ball is kickable
	 * @return true if kickable and not tackle dead and ball reliable
	 */
	public boolean canKick(Ball b, double security) {
		return this.tackledeadCountdown == 0 && b.isReliable()
				&& this.getKickDistance() - security > this.getDistance(b);
	}

	/**
	 * <code>canCatch</code> checks if player can catch the ball from his
	 * current position Based on MT 3.4.6
	 * @param b the ball
	 * @param world the world
	 * @return true if the goalie can catch the ball
	 * 
	 */
	public final boolean canCatch(Ball b, WorldModel world) {

		// TODO Smarqoo, Jan 21, 2005, 11:51:06 PM, check what means "ball
		// inside catchable area"
		// does it mean the position of the ball, or the whole ball is inside
		// (this version)?
		// TODO Smarqoo, Jan 21, 2005, 11:59:06 PM, check ban cycles?
		if (this.isGoalie() && b.isReliable() && world.inOwnPenaltyArea(b)
				&& this.getTackledeadCountdown() == 0) {
			// angle between ball & BodyDir
			Vektor toBall = b.getPosition().sub(this.getPosition());
			final double catchAngle = Vektor.normalize(toBall.getAngle()
					- this.getBodyDir());
			final double catchL = SConf.getInstance().CATCHABLE_AREA_L
					- SConf.getInstance().MARGIN_NOISE;
			// CATCHABLE_AREA_L first
			if (toBall.getLength() + SConf.getInstance().BALL_SIZE > catchL) {
				// ball further than we can reach
				return false;
			} // end if

			// check if catchAngle between MAX-/MIN- MOMENT
			if ((catchAngle > 0 && catchAngle < SConf.getInstance().CATCH_MAXMOMENT)
					|| (catchAngle < 0 && catchAngle > SConf.getInstance().CATCH_MINMOMENT)) {
				// in this case, we don't have to check the CATCHABLE_AREA_W,
				// we can catch in the middle of the catchable area
				return true;
			} // end if

			// now the tricky part, CATCHABLE_AREA_W needs to be checked
			// let's do it using sinus, we need the angle first
			final double catchW = SConf.getInstance().CATCHABLE_AREA_W
					- SConf.getInstance().MARGIN_NOISE;
			double alpha = (catchAngle > SConf.getInstance().CATCH_MAXMOMENT) ? catchAngle
					- SConf.getInstance().CATCH_MAXMOMENT
					: catchAngle - SConf.getInstance().CATCH_MINMOMENT;
			alpha = Math.abs(alpha);
			// okay, let's go
			final double deltaW = Math.sin(Math.toRadians(alpha))
					* toBall.getLength();
			// we only need to substract now
			if ((deltaW + SConf.getInstance().BALL_SIZE) <= catchW) {
				return true;
			} // end if
		}

		return false;
	} // end of method canCatch()

	/**
	 * @param tackledeadCountdown
	 *            The tackledeadCountdown to set.
	 */
	public void setTackledeadCountdown(int tackledeadCountdown) {

		this.tackledeadCountdown = tackledeadCountdown;
	}

	/**
	 * adds a state to the player.
	 * 
	 * @param state -
	 *            the state to add
	 * @param playModes -
	 *            the play modes when the state is valid
	 */
	public void addKnownState(STATES state, LinkedList<PLAY_MODE> playModes) {
		if (this.knownStates.containsKey(state)) {
			System.out.println("Player " + this.getNumber()
					+ " State already contained. " + state.toString());
			return;
		}

		this.knownStates.put(state, playModes);
	}

	/**
	 * adds a playmode to the list.
	 * 
	 * @param state -
	 *            state to add the play mode to
	 * @param mode -
	 *            play mode to add
	 */
	public void addPlayModeToState(STATES state, PLAY_MODE mode) {
		LinkedList<PLAY_MODE> modes = this.getPlayModes(state);
		if (modes == null) {
			modes = new LinkedList<PLAY_MODE>();
			modes.add(mode);
			this.addKnownState(state, modes);
		} else {
			modes.add(mode);
		}
	}

	/**
	 * all play_modes to a given state
	 * 
	 * @param state
	 * @return play modes
	 */
	public LinkedList<PLAY_MODE> getPlayModes(STATES state) {
		return this.knownStates.get(state);
	}

	/**
	 * player contains the state
	 * 
	 * @param state
	 * @return true if contained
	 */
	public boolean knowsState(STATES state) {
		return this.knownStates.containsValue(state);
	}

	/**
	 * returns the best state for a player based on the worlmodel and a given
	 * ball.
	 * 
	 * @param ball
	 */
	public void calculateBestState(Ball ball) {
		if (this.myStateEval == null) {
			return;
		}

		this.myStateEval.calculateBestState(this, ball);
	}

	/**
	 * @return Returns the myStateEval.
	 */
	public StateEvaluation getMyStateEval() {

		return this.myStateEval;
	}

	/**
	 * @param myStateEval
	 *            The myStateEval to set.
	 */
	public void setMyStateEval(StateEvaluation myStateEval) {

		this.myStateEval = myStateEval;
	}

	/**
	 * @return Returns the lastState.
	 */
	public STATES getLastState() {

		return this.lastState;
	}

	/**
	 * @deprecated
	 */
	public double getPointDirection() {
		return this.pointDirection;
	}

	/**
	 * @deprecated
	 */
	public void setPointDirection(double pointDirection) {
		this.pointDirection = pointDirection;
	}

	/**
	 * @param rel relative to body or head?
	 * @return returns the armDirection relative to body or head.
	 */
	public double getArmDirection(RELATIVE rel) {
		if (rel == RELATIVE.TO_HEAD)
			return Vektor.normalize(armDirection - getBodyDir()
					- getNeckAngle());
    // RELATIVE.TO_BODY
    return Vektor.normalize(armDirection - getBodyDir());
	}

	/**
	 * This method calculates the power that should be given to a dash command
	 * in order to come as close to the position as possible.
	 * 
	 * @param position
	 *            absolute position
	 * @return the power for the dash command
	 */
	public int getPowerForDash(Vektor position) {

		this.dummyVektor.copy(position);
		this.dummyVektor.subFromThis(this.getPosition());
		this.dummyVektor.subFromThis(this.getSpeed());

		this.dummyVektor2.pointAtPolar(1.0, this.getBodyDir());
		double d = this.dummyVektor.scalar(this.dummyVektor2);
		double dashParameter = d
				/ (this.getPConf().DASH_POWER_RATE * this.getEffort());

		return this.correctDashPowerByLimits(dashParameter);
	}

	/**
	 * This method calculates the power that should be given to a dash command
	 * in order to come as close to the position as possible. it differs from
	 * the simple version of this method in that the drift is considered. (i.e.:
	 * if you wan't to be at a position in 2 cycles it doesn't make sense to
	 * dash full first and stop afterwards (looses 1 action) instead the speed
	 * resulting from dashing + the drift in the next cycle should bring you to
	 * the position)
	 * 
	 * @param position the position
	 * @param time -
	 *            time when to arrive at position
	 * @return the power of the dash
	 */
	public int getPowerForDash(Vektor position, int time) {

		if (time == 1)
			return this.getPowerForDash(position);

		if (time < 1) {
			throw new RuntimeException("got wrong time:" + time);
		}

		// Vektor from nextPlayerPos to position
		this.dummyVektor.copy(position);
		this.dummyVektor.subFromThis(this.getPosition());
		this.dummyVektor2.copy(this.getSpeed());
		this.dummyVektor2.mult(RCMath.calculateRow(
				this.getPConf().PLAYER_DECAY, time));
		this.dummyVektor.subFromThis(this.dummyVektor2);

		// Einheitsvektor in body dir.
		this.dummyVektor2.pointAtPolar(1.0, this.getBodyDir());

		double d = this.dummyVektor.scalar(this.dummyVektor2);
		double dashParameter = d
				/ (this.getPConf().DASH_POWER_RATE * this.getEffort());
		dashParameter /= RCMath
				.calculateRow(this.getPConf().PLAYER_DECAY, time);

		return this.correctDashPowerByLimits(dashParameter);
	}

	/**
	 * correct the dash power if dashing brings us about the max speed. This
	 * should occure only for some het. players. Saves up to 40% stamina!!
	 * 
	 * @param power -
	 *            the dash power (-100;100)
	 * @return the corrected dash power
	 */
	private int correctDashPowerByLimits(double power) {
		if (power > SConf.getInstance().MAXPOWER)
			power = SConf.getInstance().MAXPOWER;
		if (power < -SConf.getInstance().MAXPOWER)
			power = -SConf.getInstance().MAXPOWER;

		double speed = this.getSpeed().getLength();

		// added some security
		double resAccel = Math.abs(power * this.getPConf().DASH_POWER_RATE
				* this.getEffort() * 0.95);
		double resSpeed = speed + resAccel;

		if (resSpeed > this.getPConf().PLAYER_SPEED_MAX) {
			// System.out.println("Cycle: " + this.getCycle() + " P:" +
			// this.getNumber() + " DASHING TO MUCH");
			// System.out.println("\t speed:" + RCMath.round(speed,4)
			// + " dash:" + RCMath.round(power,4)
			// + " dRate:" + RCMath.round(this.getPConf().DASH_POWER_RATE,4)
			// + " eff:" + RCMath.round(this.getEffort(),4)
			// );
			// System.out.println("\t resAccel:" + RCMath.round(resAccel,4)
			// + " maxAccel:" + this.pConf.PLAYER_ACCEL_MAX);
			// System.out.println("\t resSpeed:" + RCMath.round(resSpeed,4)
			// + " maxSpeed:" + this.pConf.PLAYER_SPEED_MAX);

			power *= (resAccel - (resSpeed - this.getPConf().PLAYER_SPEED_MAX))
					/ resAccel;
			// System.out.println("\t resultingDash:" + power);
		}

		return (int) power;
	}

	/**
	 * This method retrieves the absolute armDirection the agent is pointing to!
	 * 
	 * @return the absolute angle
	 */
	public double getAbsoluteArmDirection() {
		return Vektor.normalize(armDirection);
	}

	/**
	 * @param armDirection
	 *            The armDirection to set.
	 */
	public void setArmDirection(double armDirection) {
		this.armDirection = armDirection;
	}

	/**
	 * @return Returns the armDistance.
	 */
	public double getArmDistance() {
		return armDistance;
	}

	/**
	 * @param armDistance
	 *            The armDistance to set.
	 */
	public void setArmDistance(double armDistance) {
		this.armDistance = armDistance;
	}

	/**
	 * @return Returns the armExpires.
	 */
	public int getArmExpires() {
		return armExpires;
	}

	/**
	 * @param armExpires
	 *            The armExpires to set.
	 */
	public void setArmExpires(int armExpires) {
		this.armExpires = armExpires;
	}

	/**
	 * @return Returns the armMovable.
	 */
	public boolean isArmMovable() {
		return armMovable;
	}

	/**
	 * @param armMovable
	 *            The armMovable to set.
	 */
	public void setArmMovable(boolean armMovable) {
		this.armMovable = armMovable;
	}

	/**
	 * @return true if the status was changed
	 */
	public boolean isStatusChanged() {
		return statusChanged;
	}

	/**
	 * @param statusChanged set the statuschange
	 */
	public void setStatusChanged(boolean statusChanged) {
		this.statusChanged = statusChanged;
	}

	/**
	 * sets the message type
	 * @param bodyCycle the bodycycle of the player
	 * 
	 * @param number
	 * @param messageType
	 */
	public void setLastHeared(int bodyCycle, int number, MESSAGES messageType) {
		this.lastHearedInfo.bodyCycle = bodyCycle;
		this.lastHearedInfo.setNumber(number);
		this.lastHearedInfo.setMessageType(messageType);
	}

	/**
	 * 
	 * @return the body cycle of the last hear message
	 */
	public int getLastHearBodyCycle() {
		return this.lastHearedInfo.bodyCycle;
	}

	/**
	 * 
	 * @return the last heared player number
	 */
	public int getLastHearedPlayer() {
		return this.lastHearedInfo.getNumber();
	}

	/**
	 * 
	 * @return the last heared message type
	 */
	public MESSAGES getLastHearedMessageType() {
		return this.lastHearedInfo.messageType;
	}

	/**
	 * @return Returns the roleNumber.
	 */
	public int getRoleNumber() {
		if (this.roleNumber < 0 && this.isFriend)
			return this.number;

		return this.roleNumber;
	}

	/**
	 * @param roleNumber
	 *            The roleNumber to set.
	 */
	public void setRoleNumber(int roleNumber) {

		this.roleNumber = roleNumber;
	}

	/**
	 * @return Returns the ballPossessions.
	 */
	public RingList<TEAM> getBallPossessions() {

		return this.ballPossessions;
	}

	/**
	 * @return Returns the wasOffsideWhenPassed.
	 */
	public boolean wasOffsideWhenPassed() {

		return this.wasOffsideWhenPassed;
	}

	/**
	 * @param wasOffsideWhenPassed
	 *            The wasOffsideWhenPassed to set.
	 */
	public void setWasOffsideWhenPassed(boolean wasOffsideWhenPassed) {

		this.wasOffsideWhenPassed = wasOffsideWhenPassed;
	}

	/**
	 * @return Returns the kickedStandard.
	 */
	public boolean kickedStandard() {

		return this.kickedStandard;
	}

	/**
	 * @param kickedStandard
	 *            The kickedStandard to set.
	 */
	public void setKickedStandard(boolean kickedStandard) {

		this.kickedStandard = kickedStandard;
	}

}
